iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 20
2
Software Development

Functional Programming in JS系列 第 20

Functor 1: 概念篇

  • 分享至 

  • xImage
  •  

接下來進入 FP 離奇的部分了,聽說一進入這領域就會喪失跟別人解釋的能力,自己也不是這麼熟,若有錯誤或建議歡迎底下留言。


A functor is simply something that can be mapped over

這句話分成兩部分,一個是 something 一個是 can be mapped over,只要符合這兩個就是 Functor 了。

Something (或稱 Category)

A set of values arranged in some shape

https://ithelp.ithome.com.tw/upload/images/20200920/20106426THMP7yqp7R.jpg
↑ The values are in yellow, and the shape is in blue

最容易懂的例子就是 Array

An array is a set (or a collection) of values,because it’s a list of values

console.log([ 2, 4, 6 ].map(x => x + 3))
// => [ 5, 7, 9 ]

不止 Array 是 Something,任何原始型別 ( stringnumber... ) 或是 Object 甚至 Function 都是 Something

// Something 1: Array
[1,2,3,4,5] // 被存放在 Array 裡的一組值

// Something 2: Object
{ age: 22, name: 'Hannah'} // 被存在物件裡的一組值

// Something 3: Single value
39 // 某個值,除了 Number 也可以是任何型別

Can be mapped over

代表你可以把 list 中每一個值做一些事然後輸出

[ 1, 2, 3, 4, 5 ].map(x => x + 3)); // [ 4, 5, 6, 7, 8 ]

https://ithelp.ithome.com.tw/upload/images/20200920/20106426g7XxfblKbU.jpg

list 就是 [1, 2, 3, 4, 5, 6] ,每一個值經過 x => x + 3 這個 function 後 map 到新的 list [4, 5, 6, 7, 8]

input map 到的 output,shape and structure 會相同

注: 有人也會說輸入輸出的數據結構相同 ,但有人建議我說 JS 是弱型別,所以這樣翻很奇怪,可能用 Type 類別 (比較像 js Class 不是型別) 相同會比較適合、同義詞還有 Context 等。

Q: 以下範例是 Functor 嗎?

const inputObj = { age: 22, grade: 10} // 被存在物件裡的一組值
let outputObj = {};

for (const key of Object.keys(inputObj)) {
  outputObj[key] = inputObj[key] + 1
}
// { age: 23, grade: 11}

A: 輸入是 object 輸出也是 object,type 相同所以是 Functor

Q: 那假如像以下輸入輸出型別不一樣呢 ?

[1,2,3].map(x => x.toString()) // ['1', '2', '3',]

https://ithelp.ithome.com.tw/upload/images/20200920/20106426XQ1XljvKqJ.jpg

A: 雖然輸入型別是 number ,輸出型別是 string 並不相同,但 type 類別相同所以也是 Functor

Q: 那若像以下星星形狀 map 到圓圈是 Functor 嗎?

// 範例 3,context 不同,一個是 Array 一個是 Object 所以不是 
Functor[1,2,3,4,5] -> { 1: '1', 2: '2', 3: '3', 4: '4', 5: '5'}

https://ithelp.ithome.com.tw/upload/images/20200920/20106426THVRYnPFbs.jpg

A: 不是的,因為他們 type (context) 不同

Functor 除了上面說的 Array、Obj、Map、Set...也都是 Functor,另外也可以長得像下圖這樣,Number 這個值包在 NumberBox 這個 data Type 下
https://ithelp.ithome.com.tw/upload/images/20200920/20106426OOEi6czLyZ.jpg

const NumberBox = number => ({
  // 這裡就是 fmap, 在 js key 通常會寫 map
  fmap: fn => NumberBox(fn(number)), 
  value: number,
});

NumberBox 這種 一個盒子 context (shape) 裡裝著一組數字 (Value) 也符合 Something 定義,若也滿足 fmap can be map over 特性也就是 Functor, 這個盒子相當重要,不過這邊就先有個記憶就好。

所以簡單來說 Functor

傳入一個函式改變內部的資料,但維持外殼不變

也可以用以下來檢視,因為 Functor 必須滿足

單元律

a.map(x => x) === a

保存原有數據結構(可组合)

a.map(x => f(g(x))) === a.map(g).map(f)

提供接口往裡面塞值

Effect.of = value => Effect(() => value)

參考文章

如有錯誤或需要改進的地方,拜託跟我說。
我會以最快速度修改,感謝您

歡迎追蹤我的部落格,除了技術文也會分享一些在矽谷工作的甘苦。


上一篇
Pointfree
下一篇
Functor 2: 圖解 Box Data Type
系列文
Functional Programming in JS30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

5
良葛格
iT邦新手 2 級 ‧ 2020-10-07 09:56:08

Functor 這名詞,在不同語言中有不同的解釋,C++ 中也使用 Functor 這名詞,不過並不是這邊討論的 Functor。

因為這邊討論 FP,就用 Haskell 來說明,對於 Haskell 來說,Functor 這東西,簡單來說,就是一個具有 fmap 行為的型態:

class Functor type where 
    fmap :: (a -> b) -> (type a -> type b)

括號只是為了閱讀上便於理解而加上的,其實可以不用,簡單來說,型態 type 若實作了 fmap 的行為,它的 fmap 接受函式 a -> b,也就是若 fmap (a -> b),會傳回 type a -> type b 函式,這個函式可以將 type a 轉為 type b

不過在 JavaScript 這門語言中,其實沒有對等的東西,只能實作類似的概念,這是為什麼從 JavaScript 語言來理解 Functor,往往充滿誤解的原因。

JavaScript 中最常被稱為 Functor 的形態是 Array,畢竟它可以用 map 方法來模擬一下,先定義一個 fmap

function fmap(f) {
    return array => Array.prototype.map.call(array, f);
}

有了這個 fmap,若給他一個 x => `${x}` 呢?

const arr2arr = fmap(x => `${x}`);

x => `${x}`是個 a -> b 的函式,anumber,b 是 string,傳回的函式指定給 arr2arr,它是個 type a -> type b 的函式,只不過 type 在這邊是指 [],也就是說,若指定給 arr2arr 的引數是 [number],傳回的會是 [string]:

console.log(arr2arr([1])); // ['1']

就以上的模擬來說,Arrayfmap 近似了 Functor 的概念。

嗯?fmap 不是指 flatMap 嗎?不是!JavaScript 中特定物件的 flatMap,其實是 Monad 的概念。

在上面是為了容易對照 type a -> type b,採取了 [1] -> ['1'],其實也可以 [1, 2, 3] -> ['1', '2', '3']:

console.log(arr2arr([1, 2, 3])); // ['1', '2', '3']

簡單來說,就是 [numbers] -> [strings] 的意思。

因此,JavaScript 中最類似 Functor 的實作之一是 Array,因為它可以用 map 方法來模擬;那麼你的這個範例是 Functor 嗎?不是!

const inputObj = { age: 22, grade: 10} // 被存在物件裡的一組值
let outputObj = {};

for (const key of Object.keys(inputObj)) {
  outputObj[key] = inputObj[key] + 1
}

不能說是,然而如果你這麼做:

function fmap(mapper) {
    return o => {
        let outputObj = {};
        for(const key of Object.keys(o)) {
          outputObj[key] = mapper(o[key]);
        }
        return outputObj;
    };
}

const o2o = fmap(v => v + 1);
console.log(o2o({age: 22, grade: 10})); // { age: 23, grade: 11 }

那我會說 Objectfmap 近似了 Functor 的概念,fmap(v => v + 1) 可以接受 number -> number,傳回 Object(number) -> Object(number)

因此這句話「Functor 除了上面說的 Array、Obj、Map、Set...也都是 Functor」作為結論是有待商確的,若這是結論,我只要能實作出接受 a -> b,傳回 type a -> type b,那任何在 JavaScript 中的形態,都可以是 Functor 了。

記得,JavaScript 中沒有對等於 Functor 的實體,不能說直接稱某形態是 Functor,只能模擬出類似的概念。

hannahpun iT邦新手 3 級 ‧ 2020-10-07 10:05:17 檢舉

難怪找尋參考資料時都覺得每篇解釋得不一樣,原來是 "JavaScript 中沒有對等於 Functor 的實體"
若是單講滿足 fmap 特性就是 Functor 那的確 JS 所有都是 Functor

dannypsnl iT邦新手 5 級 ‧ 2022-10-13 03:25:04 檢舉

全都要談的話閃不開 category,先假設 a -> b 的型別就是 object a 到 b 的 morphism
那麼 functor F 就是說你有兩個 category,讓我們用 C 跟 L 來代替,a, b 是 C 的 object 的話,那麼 F a, F b 是 L 的 object。並且必須保留 a 跟 b 的結構(morphism 映射過去),才能稱之為 functor
尷尬的是 Haskell 雖然盡可能以 category 為理論指導,但是又得兼顧身為程式語言的實用性,所以常見的 fmap 只是特殊的 functor
從 Hask category 映射到 Hask category,事實上是 endofunctor(在同一個 category 裡面的 functor)
但是更泛用的版本基本上還蠻難用的,因為只能是嵌入或是其他近似,所以還是讓 category theory 留做理論指導即可xd
不過這些倒是可以說明為什麼 Haskell 會特別說明 functor laws 並要求開發者遵循

我要留言

立即登入留言